var fs = require('fs');
var crypto = require('crypto');
var request = require('request');
module.exports = function (option) {
  option = option || {};
  var _this = this;
  this.baseRouter = option.baseRouter || '/tencent'
  this.app = option.app;
  this.login = option.login;
  this.option = option || {};
  this.option.miniappid = option.miniappid || option.appid;
  this.option.minisecret = option.minisecret || option.secret;
  this.auth = {
    //小程序code登录
    code2Session: function (code, next) {
      a.node.get(a.string('https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code').Format(_this.option.miniappid, _this.option.minisecret, code), function (ret) {
        if (ret.openid) {
          next(ret);
        } else {
          next({
            errorMsg: ret.errmsg || 'jscode2session未知错误'
          });
        }
      })
    },
    decryptData: function (encryptedData, iv, sessionKey) {
      // base64 decode
      encryptedData = new Buffer(encryptedData, 'base64')
      iv = new Buffer(iv, 'base64')

      try {
        // 解密
        var decipher = crypto.createDecipheriv('aes-128-cbc', new Buffer(sessionKey, 'base64'), iv)
        // 设置自动 padding 为 true，删除填充补位
        decipher.setAutoPadding(true)
        var decoded = decipher.update(encryptedData, 'binary', 'utf8')
        decoded += decipher.final('utf8')

        decoded = JSON.parse(decoded)

      } catch (err) {
        throw new Error('Illegal Buffer')
      }

      if (decoded.watermark.appid !== _this.option.miniappid) {
        throw new Error('Illegal Buffer')
      }

      return decoded
    },
    access_token: '',
    getAccessToken: function (next) {
      var self = this;
      if (this.access_token && (new Date().getTime() - this.access_token.time) < 1200 * 1000) {
        next(this.access_token);
        return false;
      }
      a.node.get(a.string('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s').Format(_this.option.miniappid, _this.option.minisecret), function (ret) {
        if (ret.access_token) {
          ret.time = new Date().getTime();
          self.access_token = ret;
          next(self.access_token);
        } else {
          next({
            errorMsg: ret.errmsg || 'getAccessToken未知错误'
          });
        }
      })
    }
  }
  this.formatRet = function(ret){
    try{
      if(typeof ret == 'string' && ret.charAt(0) == '{') ret = JSON.parse(ret);
      if(ret.errcode != 0 && ret.errmsg != 'ok') ret.errorMsg = ret.errmsg;
      if(ret.errcode == 40001 && ret.errmsg.indexOf('access_token is invalid') > -1) {
        ret.errtoken = true;
        _this.auth.access_token = '';
      }
    }catch(e){}
    return ret;
  }
  this.ai = {
    printedTextOCR:function(img_url,next){
      _this.auth.getAccessToken(function asycn (ret) {
        if(ret.access_token){
          a.node.fetch({
            url:`https://api.weixin.qq.com/cv/ocr/comm?access_token=${ret.access_token}&img_url=${img_url}`
          }).then(ret => {
            if(ret.errcode) ret.errorMsg = ret.errmsg
            next(ret)
          })
        }
      })
    }
  }

  this.jssdk = {
    sha1(str){
      let shasum = crypto.createHash("sha1")
      shasum.update(str)
      str = shasum.digest("hex")
      return str
    },
    
    /**
     * 对参数对象进行字典排序
     * @param  {对象} args 签名所需参数对象
     * @return {字符串}    排序后生成字符串
     */
    raw(args) {
      var keys = Object.keys(args)
      keys = keys.sort()
      var newArgs = {}
      keys.forEach(function (key) {
        newArgs[key.toLowerCase()] = args[key]
      })
    
      var string = ''
      for (var k in newArgs) {
        string += '&' + k + '=' + newArgs[k]
      }
      string = string.substr(1)
      return string
    },
    getSign(jsapi_ticket,url){
      var ret = {
        url,
        jsapi_ticket,
        nonceStr: Math.random().toString(36).substr(2, 15),
        timestamp: parseInt(new Date().getTime() / 1000) + ''
      }
      var string = this.raw(ret)
      ret.signature = this.sha1(string)
      return ret
    },
    ticket:'',
    async getTicket(){
      return new Promise(next => {
        if(this.ticket && (new Date().getTime() - this.ticket.time) < 1200 * 1000){
          next(this.ticket)
        }
        _this.auth.getAccessToken(async (token) => {
          let ret = await a.node.fetch({
            url:`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${token.access_token}&type=jsapi`
          })
          this.ticket = ret
          next(ret)
        })
      })
    },
    async getConfig(option){
      return {
        appId:_this.option.miniappid,
        ...option,
        ...this.getSign((await this.getTicket()).ticket,option.url)
      }
    }
  }

  this.wxacode = {
    //获取限量小程序码
    get:function(args,next){
      _this.auth.getAccessToken(function (ret) {
        if (ret.access_token) {
          args = args || {};
          args.is_hyaline = args.is_hyaline == undefined ? true : args.is_hyaline;
          a.node.fetch({
            method:'POST',
            url:'https://api.weixin.qq.com/wxa/getwxacode?access_token=' + ret.access_token,
            dataType:'text',
            encoding:'binary',
            data:args
          }).then(ret => {
            ret = _this.formatRet(ret);
            if (ret.errcode == 45009) ret.errorMsg = '操作太频繁,请稍后再试!';
            if (ret.errcode == 41030) ret.errorMsg = '此功能尚未开放!';
            if(ret.errtoken){
              _this.wxacode.get(args,next);
            }else{
              next(ret);
            }
          })
        } else {
          next(ret)
        }
      })
    },
    //获取不限量小程序码
    getUnlimited: function (args, next) {
      _this.auth.getAccessToken(function (ret) {
        if (ret.access_token) {
          args = args || {};
          args.scene = args.scene || '';
          //args.is_hyaline = args.is_hyaline == undefined ? true : args.is_hyaline;
          a.node.fetch({
            path: 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' + ret.access_token,
            method: 'POST',
            dataType: 'text',
            encoding: 'binary',
            data:args
          }).then(ret => {
            ret = _this.formatRet(ret);
            if (ret.errcode == 45009) ret.errorMsg = '操作太频繁,请稍后再试!';
            if (ret.errcode == 41030) ret.errorMsg = '此功能尚未开放!';
            if(ret.errtoken){
              _this.wxacode.get(args,next);
            }else{
              next(ret);
            }
          })
        } else {
          next(ret)
        }
      })
    }
  }

  this.subscribeMessage = {
    send:function(args,next){
      _this.auth.getAccessToken(function (ret) {
        if (ret.access_token) {
          request({
            url: 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=' + ret.access_token,
            method: 'POST',
            body: JSON.stringify(args)
          }, function (err, response, ret) {
            ret = _this.formatRet(ret);
            if(ret.errtoken){
              _this.subscribeMessage.send(args,next);
            }else{
              next(ret);
            }
          })
        } else {
          next(ret)
        }
      })
    }
  }

  this.option.WXPay = {
    appid:this.option.appid,
    mch_id:this.option.mch_id,
    partner_key:this.option.partner_key,
    pfx:this.option.pfx ? fs.readFileSync(this.option.pfx) : ''
  }

  function WXPay() {
    if (!(this instanceof WXPay)) return new WXPay(arguments[0]);
    this.options = arguments[0];
    this.wxpayID = {
      appid: this.options.appid,
      mch_id: this.options.mch_id
    };
  }

  WXPay.mix = function () {
    switch (arguments.length) {
      case 1:
        var obj = arguments[0];
        for (var key in obj) {
          if (WXPay.prototype.hasOwnProperty(key)) {
            throw new Error('Prototype method exist. method: ' + key);
          }
          WXPay.prototype[key] = obj[key];
        }
        break;
      case 2:
        var key = arguments[0].toString(),
          fn = arguments[1];
        if (WXPay.prototype.hasOwnProperty(key)) {
          throw new Error('Prototype method exist. method: ' + key);
        }
        WXPay.prototype[key] = fn;
        break;
    }
  }

  WXPay.mix('option', function (option) {
    for (var k in option) {
      this.options[k] = option[k];
    }
  })

  WXPay.mix('sign', function (param, isSHA256) {
    var querystring = Object.keys(param).filter(function (key) {
      return param[key] !== undefined && param[key] !== '' && ['pfx', 'partner_key', 'sign', 'key'].indexOf(key) < 0;
    }).sort().map(function (key) {
      return key + '=' + param[key];
    }).join("&") + "&key=" + this.options.partner_key;
    if (isSHA256) {
      return crypto.createHmac('sha256', this.options.partner_key).update(querystring, 'utf8').digest('hex').toUpperCase()
    } else {
      return crypto.createHash('md5').update(querystring).digest('hex').toUpperCase()
    }
  })

  WXPay.mix('createUnifiedOrder', function (opts, fn) {
    opts.appid = opts.appid || _this.option.appid;
    opts.nonce_str = opts.nonce_str || a.node.generateNonceString();
    a.node.mix(opts, this.wxpayID);
    opts.sign = this.sign(opts);
    request({
      url: "https://api.mch.weixin.qq.com/pay/unifiedorder",
      method: 'POST',
      body: a.node.buildXML(opts),
      // agentOptions: {
      //   pfx: this.options.pfx,
      //   passphrase: this.options.mch_id
      // }
    }, function (err, response, body) {
      a.node.parseXML(body, fn);
    })
  })

  WXPay.mix('createMiniAppPay', function (opts) {
    var _options = {
      appId: _this.option.appid,
      timeStamp: Math.floor(Date.now() / 1000) + "",
      nonceStr: opts.out_trade_no,
      package: 'prepay_id=' + opts.prepay_id,
      signType: 'MD5'
    }
    return {
      ..._options,
      paySign: this.sign(_options)
    }
  })

  WXPay.mix('getBrandWCPayRequestParams', function (order, fn) {
    order.trade_type = "JSAPI";
    var _this = this;
    this.createUnifiedOrder(order, function (err, data) {
      var reqparam = {
        appId: _this.options.appid,
        timeStamp: Math.floor(Date.now() / 1000) + "",
        nonceStr: data.nonce_str,
        package: "prepay_id=" + data.prepay_id,
        signType: "MD5"
      };
      reqparam.paySign = _this.sign(reqparam);
      fn(err, reqparam);
    })
  })

  WXPay.mix('createMerchantPrepayUrl', function (param) {
    param.time_stamp = param.time_stamp || Math.floor(Date.now() / 1000);
    param.nonce_str = param.nonce_str || a.node.generateNonceString();
    a.node.mix(param, this.wxpayID);
    param.sign = this.sign(param);
    var query = Object.keys(param).filter(function (key) {
      return ['sign', 'mch_id', 'product_id', 'appid', 'time_stamp', 'nonce_str'].indexOf(key) >= 0;
    }).map(function (key) {
      return key + "=" + encodeURIComponent(param[key]);
    }).join('&');
    return "weixin://wxpay/bizpayurl?" + query;
  })

  WXPay.mix('useWXCallback', function (fn) {
    return function (req, res, next) {
      var _this = this;
      res.success = function () {
        res.end(a.node.buildXML({
          xml: {
            return_code: 'SUCCESS'
          }
        }));
      };
      res.fail = function () {
        res.end(a.node.buildXML({
          xml: {
            return_code: 'FAIL'
          }
        }));
      };
      a.node.pipe(req, function (err, data) {
        var xml = data.toString('utf8');
        a.node.parseXML(xml, function (err, msg) {
          req.wxmessage = msg;
          fn.apply(_this, [msg, req, res, next]);
        })
      })
    }
  })

  WXPay.mix('queryOrder', function (query, fn) {
    if (!(query.transaction_id || query.out_trade_no)) {
      fn(null, {
        return_code: 'FAIL',
        return_msg: '缺少参数'
      });
    }
    query.nonce_str = query.nonce_str || a.node.generateNonceString();
    a.node.mix(query, this.wxpayID);
    query.sign = this.sign(query);
    request({
      url: "https://api.mch.weixin.qq.com/pay/orderquery",
      method: "POST",
      body: a.node.buildXML({
        xml: query
      })
    }, function (err, res, body) {
      a.node.parseXML(body, fn);
    })
  })

  WXPay.mix('closeOrder', function (order, fn) {
    if (!order.out_trade_no) {
      fn(null, {
        return_code: "FAIL",
        return_msg: "缺少参数"
      });
    }

    order.nonce_str = order.nonce_str || a.node.generateNonceString();
    a.node.mix(order, this.wxpayID);
    order.sign = this.sign(order);
    request({
      url: "https://api.mch.weixin.qq.com/pay/closeorder",
      method: "POST",
      body: a.node.buildXML({
        xml: order
      })
    }, function (err, res, body) {
      a.node.parseXML(body, fn);
    })
  })

  WXPay.mix('refund', function (order, fn) {
    if (!(order.transaction_id || order.out_refund_no)) {
      fn(null, {
        return_code: 'FAIL',
        return_msg: '缺少参数'
      });
    }
    order.appid = _this.option.appid;
    order.mch_id = _this.option.WXPay.mch_id;
    order.nonce_str = order.nonce_str || a.node.generateNonceString();
    a.node.mix(order, this.wxpayID);
    order.sign = this.sign(order);
    request({
      url: "https://api.mch.weixin.qq.com/secapi/pay/refund",
      method: "POST",
      body: a.node.buildXML({
        xml: order
      }),
      agentOptions: {
        pfx: this.options.pfx,
        passphrase: this.options.mch_id
      }
    }, function (err, response, body) {
      a.node.parseXML(body, fn);
    })
  })
  //添加分账接收方
  WXPay.mix('profitsharingaddreceiver', function (receiver, fn) {
    var order = {};
    order.appid = _this.option.appid;
    order.mch_id = _this.option.WXPay.mch_id;
    order.receiver = JSON.stringify(receiver);
    order.nonce_str = order.nonce_str || a.node.generateNonceString();
    a.node.mix(order, this.wxpayID);
    order.sign = this.sign(order, true);
    request({
      url: "https://api.mch.weixin.qq.com/pay/profitsharingaddreceiver",
      method: "POST",
      body: a.node.buildXML({
        xml: order
      }),
    }, function (err, response, body) {
      a.node.parseXML(body, fn);
    })
  })
  WXPay.mix('profitsharingaddreceivers', function (receivers, fn) {
    var promises = [],
      _this = this,
      _err = null;
    for (var i = 0; i < receivers.length; i++) {
      promises.push(new Promise(function (resolve, reject) {
        var receiver = {
          type: receivers[i].type || 'PERSONAL_OPENID',
          account: receivers[i].account,
          name: receivers[i].name,
          relation_type: receivers[i].relation_type
        };
        _this.profitsharingaddreceiver(receiver, function (err, ret) {
          if (ret.result_code != 'SUCCESS') {
            ret.errorMsg = receiver.name + '(' + receiver.account + ')'
            ret.errorMsg += ret.err_code_des || '添加分账接收方失败'
            _err = ret;
          }
          resolve(ret)
        })
      }))
    }
    Promise.all(promises).then(function (ret) {
      fn(_err, _err || ret[0])
    });
  })

  //单次分账
  WXPay.mix('profitsharing', function (order, fn) {
    if (!(order.transaction_id || order.out_order_no)) {
      fn(null, {
        return_code: 'FAIL',
        return_msg: '缺少参数'
      });
    }
    order.appid = _this.option.appid;
    order.mch_id = _this.option.WXPay.mch_id;
    order.receivers = JSON.stringify(order.receivers);
    order.nonce_str = order.nonce_str || a.node.generateNonceString();
    a.node.mix(order, this.wxpayID);
    order.sign = this.sign(order, true);
    request({
      url: "https://api.mch.weixin.qq.com/secapi/pay/profitsharing", //multiprofitsharing
      method: "POST",
      body: a.node.buildXML({
        xml: order
      }),
      agentOptions: {
        pfx: this.options.pfx,
        passphrase: this.options.mch_id
      }
    }, function (err, response, body) {
      a.node.parseXML(body, fn);
    })
  })
  //完结分账
  WXPay.mix('profitsharingfinish', function (order, fn) {
    if (!(order.transaction_id || order.out_order_no)) {
      fn(null, {
        return_code: 'FAIL',
        return_msg: '缺少参数'
      });
    }
    order.appid = _this.option.appid;
    order.mch_id = _this.option.WXPay.mch_id;
    order.description = order.description || '分账已完成';
    order.nonce_str = order.nonce_str || a.node.generateNonceString();
    a.node.mix(order, this.wxpayID);
    order.sign = this.sign(order, true);
    request({
      url: "https://api.mch.weixin.qq.com/secapi/pay/profitsharingfinish",
      method: "POST",
      body: a.node.buildXML({
        xml: order
      }),
      agentOptions: {
        pfx: this.options.pfx,
        passphrase: this.options.mch_id
      }
    }, function (err, response, body) {
      a.node.parseXML(body, fn);
    })
  })
  //查询分账结果
  WXPay.mix('profitsharingquery', function (order, fn) {
    if (!(order.transaction_id || order.out_order_no)) {
      fn(null, {
        return_code: 'FAIL',
        return_msg: '缺少参数'
      });
    }
    order.mch_id = _this.option.WXPay.mch_id;
    order.nonce_str = order.nonce_str || a.node.generateNonceString();
    order.sign = this.sign(order, true);
    request({
      url: "https://api.mch.weixin.qq.com/pay/profitsharingquery",
      method: "POST",
      body: a.node.buildXML({
        xml: order
      }),
    }, function (err, response, body) {
      a.node.parseXML(body, fn);
    })
  })

  this.WXPay = WXPay(option.WXPay);

  this.qy = {
    access_token:'',
    getToken:function(next){
      request({
        url: "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + option.qyId + "&corpsecret=" + option.qySecret,
        method: "GET"
      }, function (err, response, body) {
        body = JSON.parse(JSON.parse(body));
        if(body.access_token) {
          _this.qy.access_token = body.access_token;
        }else{
          body.errorMsg = '获取授权失败，请检测企业微信配置是否正确!'
        }
        next(body)
      })
    },
    getUserByMobile:function(mobile,next){
      this.getToken(function(ret){
        if(ret.errorMsg){
          next(ret);
          return false;
        }
        request({
          url: "https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token=" + ret.access_token,
          method: "POST",
          json: true,
          headers: {
            "content-type": "application/json",
          },
          body: {
            mobile
          }
        }, function (err, response, body) {
          body = JSON.parse(JSON.parse(body));
          if(!body.userid) {
            body.errorMsg = body.errmsg || '获取用户信息失败!'
          }
          next(body)
        })
      })
    },
    messageSend:function(data,next){
      this.getToken(function(ret){
        if(ret.errorMsg){
          next(ret);
          return false;
        }
        data.agentid = option.qyAgentid;
        data.type = data.type || 'text';
        request({
          url: "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + ret.access_token,
          method: "POST",
          json: true,
          headers: {
            "content-type": "application/json",
          },
          body: data
        }, function (err, response, body) {
          body = JSON.parse(JSON.parse(body));
          if(!body.userid) {
            body.errorMsg = body.errmsg || '发送信息失败!'
          }
          next(body)
        })
      })
    },
    messageSendByPhone:function(phone,data,next){
      var qyThis = this;
      this.getUserByMobile(phone,function(user){
        if(user.userid){
          data.userid = user.userid;
          data.touser = user.userid;
          qyThis.messageSend(data,function(ret){
            next(ret);
          })
        }else{
          next(user);
        }
      })
    }
  }
  /*Region列表
    北京一区（华北）	tj	<bucketname-APPID>.costj.myqcloud.com	tj.file.myqcloud.com
    北京	bj	<bucketname-APPID>.cosbj.myqcloud.com	bj.file.myqcloud.com
    上海（华东）	sh	<bucketname-APPID>.cossh.myqcloud.com	sh.file.myqcloud.com
    广州（华南）	gz	<bucketname-APPID>.cosgz.myqcloud.com	gz.file.myqcloud.com
    成都（西南）	cd	<bucketname-APPID>.coscd.myqcloud.com	cd.file.myqcloud.com
    中国香港	hk	<bucketname-APPID>.coshk.myqcloud.com	hk.file.myqcloud.com
    新加坡	sgp	<bucketname-APPID>.cossgp.myqcloud.com	sgp.file.myqcloud.com
    多伦多	ca	<bucketname-APPID>.cosca.myqcloud.com	ca.file.myqcloud.com
    法兰克福	ger	<bucketname-APPID>.cosger.myqcloud.com	ger.file.myqcloud.com
  */
  //对象存储
  this.option.cos = this.option.cos || {
    appId:this.option.cosAppId,
    SecretId:this.option.apiKeyId,
    SecretKey:this.option.apiKey,
    Bucket:this.option.cosBucket,
    Region:this.option.cosRegion
  }
  this.cos = {
    CryptoJS: (function () {
      var CryptoJS = function (g, l) {
        var e = {},
          d = e.lib = {},
          m = function () {},
          k = d.Base = {
            extend: function (a) {
              m.prototype = this;
              var c = new m;
              a && c.mixIn(a);
              c.hasOwnProperty("init") || (c.init = function () {
                c.$super.init.apply(this, arguments)
              });
              c.init.prototype = c;
              c.$super = this;
              return c
            },
            create: function () {
              var a = this.extend();
              a.init.apply(a, arguments);
              return a
            },
            init: function () {},
            mixIn: function (a) {
              for (var c in a) a.hasOwnProperty(c) && (this[c] = a[c]);
              a.hasOwnProperty("toString") && (this.toString = a.toString)
            },
            clone: function () {
              return this.init.prototype.extend(this)
            }
          },
          p = d.WordArray = k.extend({
            init: function (a, c) {
              a = this.words = a || [];
              this.sigBytes = c != l ? c : 4 * a.length
            },
            toString: function (a) {
              return (a || n).stringify(this)
            },
            concat: function (a) {
              var c = this.words,
                q = a.words,
                f = this.sigBytes;
              a = a.sigBytes;
              this.clamp();
              if (f % 4)
                for (var b = 0; b < a; b++) c[f + b >>> 2] |= (q[b >>> 2] >>> 24 - 8 * (b % 4) & 255) << 24 - 8 * ((f + b) % 4);
              else if (65535 < q.length)
                for (b = 0; b < a; b += 4) c[f + b >>> 2] = q[b >>> 2];
              else c.push.apply(c, q);
              this.sigBytes += a;
              return this
            },
            clamp: function () {
              var a = this.words,
                c = this.sigBytes;
              a[c >>> 2] &= 4294967295 <<
                32 - 8 * (c % 4);
              a.length = g.ceil(c / 4)
            },
            clone: function () {
              var a = k.clone.call(this);
              a.words = this.words.slice(0);
              return a
            },
            random: function (a) {
              for (var c = [], b = 0; b < a; b += 4) c.push(4294967296 * g.random() | 0);
              return new p.init(c, a)
            }
          }),
          b = e.enc = {},
          n = b.Hex = {
            stringify: function (a) {
              var c = a.words;
              a = a.sigBytes;
              for (var b = [], f = 0; f < a; f++) {
                var d = c[f >>> 2] >>> 24 - 8 * (f % 4) & 255;
                b.push((d >>> 4).toString(16));
                b.push((d & 15).toString(16))
              }
              return b.join("")
            },
            parse: function (a) {
              for (var c = a.length, b = [], f = 0; f < c; f += 2) b[f >>> 3] |= parseInt(a.substr(f,
                2), 16) << 24 - 4 * (f % 8);
              return new p.init(b, c / 2)
            }
          },
          j = b.Latin1 = {
            stringify: function (a) {
              var c = a.words;
              a = a.sigBytes;
              for (var b = [], f = 0; f < a; f++) b.push(String.fromCharCode(c[f >>> 2] >>> 24 - 8 * (f % 4) & 255));
              return b.join("")
            },
            parse: function (a) {
              for (var c = a.length, b = [], f = 0; f < c; f++) b[f >>> 2] |= (a.charCodeAt(f) & 255) << 24 - 8 * (f % 4);
              return new p.init(b, c)
            }
          },
          h = b.Utf8 = {
            stringify: function (a) {
              try {
                return decodeURIComponent(escape(j.stringify(a)))
              } catch (c) {
                throw Error("Malformed UTF-8 data");
              }
            },
            parse: function (a) {
              return j.parse(unescape(encodeURIComponent(a)))
            }
          },
          r = d.BufferedBlockAlgorithm = k.extend({
            reset: function () {
              this._data = new p.init;
              this._nDataBytes = 0
            },
            _append: function (a) {
              "string" == typeof a && (a = h.parse(a));
              this._data.concat(a);
              this._nDataBytes += a.sigBytes
            },
            _process: function (a) {
              var c = this._data,
                b = c.words,
                f = c.sigBytes,
                d = this.blockSize,
                e = f / (4 * d),
                e = a ? g.ceil(e) : g.max((e | 0) - this._minBufferSize, 0);
              a = e * d;
              f = g.min(4 * a, f);
              if (a) {
                for (var k = 0; k < a; k += d) this._doProcessBlock(b, k);
                k = b.splice(0, a);
                c.sigBytes -= f
              }
              return new p.init(k, f)
            },
            clone: function () {
              var a = k.clone.call(this);
              a._data = this._data.clone();
              return a
            },
            _minBufferSize: 0
          });
        d.Hasher = r.extend({
          cfg: k.extend(),
          init: function (a) {
            this.cfg = this.cfg.extend(a);
            this.reset()
          },
          reset: function () {
            r.reset.call(this);
            this._doReset()
          },
          update: function (a) {
            this._append(a);
            this._process();
            return this
          },
          finalize: function (a) {
            a && this._append(a);
            return this._doFinalize()
          },
          blockSize: 16,
          _createHelper: function (a) {
            return function (b, d) {
              return (new a.init(d)).finalize(b)
            }
          },
          _createHmacHelper: function (a) {
            return function (b, d) {
              return (new s.HMAC.init(a,
                d)).finalize(b)
            }
          }
        });
        var s = e.algo = {};
        return e
      }(Math);
      (function () {
        var g = CryptoJS,
          l = g.lib,
          e = l.WordArray,
          d = l.Hasher,
          m = [],
          l = g.algo.SHA1 = d.extend({
            _doReset: function () {
              this._hash = new e.init([1732584193, 4023233417, 2562383102, 271733878, 3285377520])
            },
            _doProcessBlock: function (d, e) {
              for (var b = this._hash.words, n = b[0], j = b[1], h = b[2], g = b[3], l = b[4], a = 0; 80 > a; a++) {
                if (16 > a) m[a] = d[e + a] | 0;
                else {
                  var c = m[a - 3] ^ m[a - 8] ^ m[a - 14] ^ m[a - 16];
                  m[a] = c << 1 | c >>> 31
                }
                c = (n << 5 | n >>> 27) + l + m[a];
                c = 20 > a ? c + ((j & h | ~j & g) + 1518500249) : 40 > a ? c + ((j ^ h ^ g) + 1859775393) : 60 > a ? c + ((j & h | j & g | h & g) - 1894007588) : c + ((j ^ h ^
                  g) - 899497514);
                l = g;
                g = h;
                h = j << 30 | j >>> 2;
                j = n;
                n = c
              }
              b[0] = b[0] + n | 0;
              b[1] = b[1] + j | 0;
              b[2] = b[2] + h | 0;
              b[3] = b[3] + g | 0;
              b[4] = b[4] + l | 0
            },
            _doFinalize: function () {
              var d = this._data,
                e = d.words,
                b = 8 * this._nDataBytes,
                g = 8 * d.sigBytes;
              e[g >>> 5] |= 128 << 24 - g % 32;
              e[(g + 64 >>> 9 << 4) + 14] = Math.floor(b / 4294967296);
              e[(g + 64 >>> 9 << 4) + 15] = b;
              d.sigBytes = 4 * e.length;
              this._process();
              return this._hash
            },
            clone: function () {
              var e = d.clone.call(this);
              e._hash = this._hash.clone();
              return e
            }
          });
        g.SHA1 = d._createHelper(l);
        g.HmacSHA1 = d._createHmacHelper(l)
      })();
      (function () {
        var g = CryptoJS,
          l = g.enc.Utf8;
        g.algo.HMAC = g.lib.Base.extend({
          init: function (e, d) {
            e = this._hasher = new e.init;
            "string" == typeof d && (d = l.parse(d));
            var g = e.blockSize,
              k = 4 * g;
            d.sigBytes > k && (d = e.finalize(d));
            d.clamp();
            for (var p = this._oKey = d.clone(), b = this._iKey = d.clone(), n = p.words, j = b.words, h = 0; h < g; h++) n[h] ^= 1549556828, j[h] ^= 909522486;
            p.sigBytes = b.sigBytes = k;
            this.reset()
          },
          reset: function () {
            var e = this._hasher;
            e.reset();
            e.update(this._iKey)
          },
          update: function (e) {
            this._hasher.update(e);
            return this
          },
          finalize: function (e) {
            var d =
              this._hasher;
            e = d.finalize(e);
            d.reset();
            return d.finalize(this._oKey.clone().concat(e))
          }
        })
      })();


      (function () {
        // Shortcuts
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var C_enc = C.enc;
        var Base64 = C_enc.Base64 = {
          stringify: function (wordArray) {
            // Shortcuts
            var words = wordArray.words;
            var sigBytes = wordArray.sigBytes;
            var map = this._map;

            // Clamp excess bits
            wordArray.clamp();

            // Convert
            var base64Chars = [];
            for (var i = 0; i < sigBytes; i += 3) {
              var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
              var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff;
              var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff;

              var triplet = (byte1 << 16) | (byte2 << 8) | byte3;

              for (var j = 0;
                (j < 4) && (i + j * 0.75 < sigBytes); j++) {
                base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f));
              }
            }

            // Add padding
            var paddingChar = map.charAt(64);
            if (paddingChar) {
              while (base64Chars.length % 4) {
                base64Chars.push(paddingChar);
              }
            }

            return base64Chars.join('');
          },
          parse: function (base64Str) {
            // Shortcuts
            var base64StrLength = base64Str.length;
            var map = this._map;

            // Ignore padding
            var paddingChar = map.charAt(64);
            if (paddingChar) {
              var paddingIndex = base64Str.indexOf(paddingChar);
              if (paddingIndex != -1) {
                base64StrLength = paddingIndex;
              }
            }

            // Convert
            var words = [];
            var nBytes = 0;
            for (var i = 0; i < base64StrLength; i++) {
              if (i % 4) {
                var bits1 = map.indexOf(base64Str.charAt(i - 1)) << ((i % 4) * 2);
                var bits2 = map.indexOf(base64Str.charAt(i)) >>> (6 - (i % 4) * 2);
                words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8);
                nBytes++;
              }
            }

            return WordArray.create(words, nBytes);
          },

          _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
        };
      }());
      return CryptoJS;
    })(),
    ..._this.option.cos,
    serveLessSign: function (url, args, meth) {
      args = args || {};
      args.SecretId = this.SecretId;
      args.SignatureMethod = 'HmacSHA1';
      args.Region = this.Region;
      args.Timestamp = parseInt(Date.now() / 1000);
      args.Nonce = parseInt(Math.random() * Math.pow(2, 32));
      //排序
      var arr = [];
      for (var key in args) arr.push(key);
      arr = arr.sort();
      var s = (meth || 'GET') + url + '?';
      for (var i = 0; i < arr.length; i++) s += (i ? '&' : '') + arr[i] + '=' + args[arr[i]];
      args.Signature = this.CryptoJS.HmacSHA1(s, this.SecretKey).toString(this.CryptoJS.enc.Base64);
      s = '';
      for (key in args) s += '&' + key + '=' + encodeURIComponent(args[key]);
      return s.replace(new RegExp('&'), '');
    },
    serveLess: function (functionName, param, callback) {
      var _this = this,
        url = 'scf.api.qcloud.com/v2/index.php';
      return 'https://' + url + '?' + _this.serveLessSign(url, {
        Action: 'InvokeFunction',
        functionName: functionName,
        param: JSON.stringify(param),
        invokeType: 'RequestResponse'
      });
    },
    cosSign: function (path) {
      var random = parseInt(Math.random() * Math.pow(2, 32));
      var now = parseInt(Date.now() / 1000);
      var e = now + 600; //签名过期时间为当前+600s
      var str = 'a=' + this.appId + '&k=' + this.SecretId + '&e=' + e + '&t=' + now + '&r=' + random + '&f=' + encodeURI('/' + this.appId + '/' + this.Bucket + path) + '&b=' + this.Bucket;
      var sha1Res = this.CryptoJS.HmacSHA1(str, this.SecretKey); // 这里使用CryptoJS计算sha1值，你也可以用其他开源库或自己实现
      var strWordArray = this.CryptoJS.enc.Utf8.parse(str);
      var resWordArray = sha1Res.concat(strWordArray);
      return resWordArray.toString(this.CryptoJS.enc.Base64);
    },
    cosUpload: function (tempFilePath) {
      var _this = this;
      var host = this.Region + '.file.myqcloud.com/files/v2/' + this.appId + '/' + this.Bucket;
      var names = tempFilePath.split('.');
      var path = '/' + a.guid() + '.' + names[names.length - 1];
      return {
        url: 'https://' + host + path,
        filePath: tempFilePath,
        method: 'POST',
        name: 'filecontent',
        header: [{
            key: 'Authorization',
            value: _this.cosSign(path)
          },
          {
            key: 'insertOnly',
            value: 0
          }
        ],
        formData: {
          'op': 'upload'
        }
      }
    },
    cosList: function () {
      var host = this.Region + '.file.myqcloud.com';
      var path = '/files/v2/' + this.appId + '/' + this.Bucket;
      var head = {
        'Authorization': this.cosSign('')
      };
      return {
        url: 'https://' + host + path + '?op=list&num=10',
        header: head,
      }
    }
  }
  //微信开放平台
  this.wxcpt = new WXCrypt(option.wxcptAPPID,option.wxcptToken,option.wxcptKey,option.wxcptAPPSECRET,option.wxcptPhone)
  function WXCrypt(appID, token, encodingAESKey, appSecret,phone) {
    this.appID = appID;
    this.token = token;
    this.aesKey = new Buffer(encodingAESKey + '=', 'base64');
    this.iv = this.aesKey.slice(0, 16);
    this.appSecret = appSecret;
    this.phone = phone;
  }
  (function(){
    WXCrypt.prototype.decryptMsg = function (msgSignature, timestamp, nonce, data) {
      const msg_encrypt = data.Encrypt;
      //if(data.ToUserName!=this.appID)throw new Error("ToUserName is invalid");
      if (this.getSignature(timestamp, nonce, msg_encrypt) != msgSignature) throw new Error('msgSignature is not invalid');
      const decryptedMessage = this.decrypt(msg_encrypt);
      return a.node.parseXML(decryptedMessage, { explicitArray: false });
    };

    WXCrypt.prototype.encryptMsg = function (replyMsg, opts) {
      const result = {};
      const options = opts || {};
      result.Encrypt = this.encrypt(replyMsg);
      result.Nonce = options.nonce || parseInt((Math.random() * 100000000000), 10);
      result.TimeStamp = options.timestamp || Date.now();

      result.MsgSignature = this.getSignature(result.TimeStamp, result.Nonce, result.Encrypt);

      return a.node.buildXML(result);
    };

    WXCrypt.prototype.encrypt = function (xmlMsg) {
      const random16 = crypto.pseudoRandomBytes(16);

      const msg = new Buffer(xmlMsg);

      const msgLength = new Buffer(4);
      msgLength.writeUInt32BE(msg.length, 0);

      const corpId = new Buffer(this.appID);

      const raw_msg = Buffer.concat([random16, msgLength, msg, corpId]);//randomString + msgLength + xmlMsg + this.corpID;
      const encoded = PKCS7Encoder(raw_msg);
      const cipher = crypto.createCipheriv('aes-256-cbc', this.aesKey, this.iv);
      cipher.setAutoPadding(false);// crypto的padding模式不是PKCS7!!!

      // const cipheredMsg = Buffer.concat([cipher.update(/*encoded*/raw_msg), cipher.final()]);
      const cipheredMsg = Buffer.concat([cipher.update(encoded), cipher.final()]);

      return cipheredMsg.toString('base64');
    };

    WXCrypt.prototype.decrypt = function (str) {
      const aesCipher = crypto.createDecipheriv("aes-256-cbc", this.aesKey, this.iv);
      aesCipher.setAutoPadding(false);
      let decipheredBuff = Buffer.concat([aesCipher.update(str, 'base64'), aesCipher.final()]);

      decipheredBuff = PKCS7Decoder(decipheredBuff);

      const len_netOrder_corpid = decipheredBuff.slice(16);

      const msg_len = len_netOrder_corpid.slice(0, 4).readUInt32BE(0);
      //recoverNetworkBytesOrder(len_netOrder_corpid.slice(0, 4));

      const result = len_netOrder_corpid.slice(4, msg_len + 4).toString();

      const appId = len_netOrder_corpid.slice(msg_len + 4).toString();

      if (appId != this.appID) throw new Error('appId is invalid');

      return result;

    };

    WXCrypt.prototype.getSignature = function (timestamp, nonce, encrypt) {
      const raw_signature = [this.token, timestamp, nonce, encrypt].sort().join('');

      const sha1 = crypto.createHash("sha1");
      sha1.update(raw_signature);

      return sha1.digest("hex");
    };

    function PKCS7Decoder(buff) {
      var pad = buff[buff.length - 1];

      if (pad < 1 || pad > 32) {
          pad = 0;
      }

      return buff.slice(0, buff.length - pad);
    }

    function PKCS7Encoder(buff) {
      const blockSize = 32;
      const strSize = buff.length;
      const amountToPad = blockSize - (strSize % blockSize);

      var pad = new Buffer(amountToPad);
      pad.fill(String.fromCharCode(amountToPad));

      return Buffer.concat([buff, pad]);
    }
    var ComponentVerifyTicket,CreateTime;
    WXCrypt.prototype.notify = function(req,res){
      if(req.body.xml.appid != this.appID) {
        res.send('success');
        return false;
      }
      var xml = this.decrypt(req.body.xml.encrypt);
      a.node.parseXML(xml,function(err,ret){
        if(!err && typeof ret == 'object'){
          if(ret.InfoType == 'component_verify_ticket'){
            ComponentVerifyTicket = ret.ComponentVerifyTicket;
            CreateTime = ret.CreateTime;
            fs.writeFileSync('./logs/ComponentVerifyTicket.txt',ret.ComponentVerifyTicket);
          }
        }
      })
      res.send('success');
    }
    var component_access_token,expires_in;
    WXCrypt.prototype.getAccessToken = function(next){
      if(!ComponentVerifyTicket){
        ComponentVerifyTicket = fs.readFileSync('./logs/ComponentVerifyTicket.txt','utf-8').toString();
        if(!ComponentVerifyTicket){
          next({errorMsg:'token正在生成中,请稍后再试!'});
          return false;
        }
      }
      request({
        url: "https://api.weixin.qq.com/cgi-bin/component/api_component_token",
        method: "POST",
        json: true,
        headers: {
          "content-type": "application/json",
        },
        body:{
          component_appid:this.appID,
          component_appsecret:this.appSecret,
          component_verify_ticket:ComponentVerifyTicket
        }
      }, function (err, response, body) {
        if(body.component_access_token) {
          component_access_token = body.component_access_token;
          expires_in = body.expires_in;
        }else{
          body.errorMsg = body.errmsg || body.errcode || '获取accessToken出错';
        }
        next(body);
      })
    }
    WXCrypt.prototype.fastregisterweappCreate = function(next,body){
      this.getAccessToken((ret)=>{
        if(ret.errorMsg){
          next(ret);
          return false;
        }
        body.component_phone = this.phone;
        request({
          url: "https://api.weixin.qq.com/cgi-bin/component/fastregisterweapp?action=create&component_access_token=" + component_access_token,
          method: "POST",
          json: true,
          headers: {
            "content-type": "application/json",
          },
          body:body
        }, function (err, response, body) {
          if(body.errcode) body.errorMsg = body.errmsg || body.errcode || '快速创建小程序出错';
          next(body);
        })
      })
    }
    WXCrypt.prototype.fastregisterweappSearch = function(next,body){
      this.getAccessToken((ret)=>{
        if(ret.errorMsg){
          next(ret);
          return false;
        }
        body.code_type = undefined;
        request({
          url: "https://api.weixin.qq.com/cgi-bin/component/fastregisterweapp?action=search&component_access_token=" + component_access_token,
          method: "POST",
          json: true,
          headers: {
            "content-type": "application/json",
          },
          body:body
        }, function (err, response, body) {
          if(body.errcode) body.errorMsg = body.errmsg || body.errcode || '查询快速创建小程序状态出错';
          next(body);
        })
      })
    }
    WXCrypt.prototype.preauthcode = function(next,body){
      var _this = this;
      this.getAccessToken((ret)=>{
        if(ret.errorMsg){
          next(ret);
          return false;
        }
        request({
          url: "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=" + component_access_token,
          method: "POST",
          json: true,
          headers: {
            "content-type": "application/json",
          },
          body:{component_appid:this.appID}
        }, function (err, response, body) {
          if(!body.pre_auth_code) {
            body.errorMsg = body.errmsg || body.errcode || '预授权码出错';
          }else{
            body.url = 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=' + _this.appID + '&pre_auth_code=' + body.pre_auth_code + '&';
          }
          next(body);
        })
      })
    }
  })()
}